/******************************************************************************************************
Copyright (C) 2017 Mestrelab Research S.L. All rights reserved.

This file is part of the Mnova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is provided AS IS
with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
*******************************************************************************************************/

/*globals qsTr, settings, Application, Env, ManageStructureData, MessageBox, MoleculeSearcher, MoleculeSearcherViewer, WebUtils, WebUtilsQT*/

/*jslint plusplus: true, indent: 4 */

function Emolecules_Engine(aUser, aAccessKey) {
	'use strict';

	//Create a WebUtils instance onto constructor method
	this.user = aUser;
	this.accessKey = aAccessKey;
	this.revisionWebUtilsQT = 16393;

	//Registry engine options
	this.enabledKey = "Molecule Searcher/Engines/" + Emolecules_Engine.ID  + "/Active";

	//Instance attributes to configure the engine
	this.features = [MoleculeSearcher.moleculeFeatureID, MoleculeSearcher.structureSearchFeatureID];
	this.id = Emolecules_Engine.ID;
	this.active = settings.value(this.enabledKey, true);

	//Engine specific configuration
	this.engine = {};
	this.engine.molecule = {};
	this.engine.ID = Emolecules_Engine.ID;
	this.engine.badRecordsCounter = 0;
	this.engine.molecule.URLImage = "{0}";
	this.engine.molecule.URLPatternToGetEncryptedID = new RegExp("src=\"(.*)\"", "mi");
	this.engine.molecule.structureDataManagementMethod = "getElementsFromXML";
	this.engine.molecule.downloadManagementMethod = "getElementsFromXML";
	this.engine.molecule.textSearchMethod = "textSearch";
	this.engine.molecule.structureSearchMethod = "structureSearch";
	this.engine.molecule.substructureSearchMethod = "substructureSearch";
	this.engine.molecule.detailMethod = "getMoleculeDetail";
	this.engine.molecule.getMethod = "structureByVID";
	this.engine.molecule.downloadMethod = "structureByVID";
	this.engine.molecule.structureSearchField = "mol";
	this.engine.molecule.substructureSearchField = "mol";
	this.engine.molecule.iDArrayElement = "compoundRows";
	this.engine.molecule.molFileXMLElement = "ns1:structureByVIDOutput";
	this.engine.molecule.searchXMLElement = 'ns1:searchOutput';
	this.engine.molecule.actualMatchsXMLElement = "a";
	this.engine.molecule.estimatePagesOfTenElementsXMLElement = "np";
	this.engine.molecule.estimateMatchsXMLElement = "e";
	this.engine.molecule.queryXMLElement = "q";
	this.engine.molecule.faultStringXMLElement = "faultstring";
	this.engine.molecule.mappedFields = {
		ID: "VID",
		"Molecular Formula": "MF",
		SMILES: "SMILES",
		"Molecular Weight": "MWT",
		ENC_ID: "depictionURL"
	};

	//GUI options configuration.
	this.engine.optionsSettings = {};
	this.engine.optionsSettings.delimiter = "_";
	this.engine.optionsSettings.guiTypeFieldNumber = 1;
	this.engine.optionsSettings.groupFeatureFieldNumber = 2;
	this.engine.optionsSettings.parameterNameFieldNumber = 3;
	this.engine.optionsSettings.engineIDFieldNumber = 4;
	this.engine.optionsSettings.comboboxGUIType = MoleculeSearcher.comboboxGUITypeID;
	this.engine.optionsSettings.checkboxGUIType = MoleculeSearcher.checkboxGUITypeID;
	this.engine.optionsSettings.spinboxGUIType = MoleculeSearcher.spinboxGUITypeID;
	this.engine.optionsSettings.exactMatchParameter = "exactMatch";
	this.engine.optionsSettings.fieldParameter = "fieldParameter";
	this.engine.optionsSettings.requiredActionDelimiter  = "_";
	this.engine.optionsSettings.requiredActionTypeFieldNumber  = 1;
	this.engine.optionsSettings.requiredActionDataFieldNumber  = 2;

	//The property must match with the UI object name. It must be coherent with definition above
	this.engine.options = {};
	this.engine.options.textSearch = {};
	this.engine.options.textSearch[this.engine.optionsSettings.checkboxGUIType + this.engine.optionsSettings.delimiter + "textSearch" + this.engine.optionsSettings.delimiter + this.engine.optionsSettings.exactMatchParameter + this.engine.optionsSettings.delimiter + this.id] = {};
	this.engine.options.textSearch[this.engine.optionsSettings.checkboxGUIType + this.engine.optionsSettings.delimiter + "textSearch" + this.engine.optionsSettings.delimiter + this.engine.optionsSettings.exactMatchParameter + this.engine.optionsSettings.delimiter + this.id].registryKey = "Molecule Searcher/Engines/" + Emolecules_Engine.ID  + "/Text Search Exact Match";

	//Control revision of the version in order to use the new library when it makes sense.
	if (Application.version.revision >= this.revisionWebUtilsQT) {
		this.webUtils = new WebUtilsQT();
	} else {
		this.webUtils = new WebUtils();
		if (!this.webUtils.isValid) {
			MessageBox.critical(qsTr("Error initializing WebUtils"));
			return;
		}
	}

	Emolecules_Engine.prototype.startSearch = function (aQuery, aParameters) {

		var url, action, xml, response, searchType, field;

		//Default values
		searchType = "ss";
		field = "all";

		if (aParameters !== undefined && aParameters[this.engine.optionsSettings.fieldParameter] !== undefined) {
			field = aParameters[this.engine.optionsSettings.fieldParameter];
		}

		if (aParameters !== undefined && aParameters[this.engine.optionsSettings.exactMatchParameter] !== undefined && aParameters[this.engine.optionsSettings.exactMatchParameter]) {
			searchType = "ex";
		}

		url = "https://search.emolecules.com/services/emolservicev3.php";
		action = "http://www.emolecules.com/services/startSearch";

		xml = '<?xml version="1.0" encoding="UTF-8"?>';
		xml += '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.emolecules.com/services">';
		xml += '<SOAP-ENV:Body>';
		xml += '<ns1:searchInput>';
		if (field === this.engine.molecule.structureSearchField) {
			xml += '<MOLString>' + aQuery + '</MOLString>';
		} else {
			xml += '<queryString>' + aQuery + '</queryString>';
		}
		xml += '<searchType>' + searchType + '</searchType>';
		if (field !== "all") {
			xml += '<searchField>' + field + '</searchField>';
		}

		xml += '<vendor></vendor>';
		xml += '<accessHash>';
		xml += '<clientSoftware>' + this.user + '</clientSoftware>';
		xml += '<clientKey>' + this.accessKey + '</clientKey>';
		xml += '</accessHash>';
		xml += '</ns1:searchInput>';
		xml += '</SOAP-ENV:Body>';
		xml += '</SOAP-ENV:Envelope>';

		response = this.webUtils.soapRequest(url, action, xml);

		return response;
	};

	Emolecules_Engine.prototype.continueSearch = function (aQuery, aParameters, aCurrentPageNumber) {

		var url, action, xml, response, searchType, field, currentPageNumber, previousPageNumber;

		//Default values
		searchType = "ss";
		field = "all";
		currentPageNumber = aCurrentPageNumber;
		previousPageNumber = aCurrentPageNumber - 1;

		if (aParameters !== undefined && aParameters[this.engine.optionsSettings.fieldParameter] !== undefined) {
			field = aParameters[this.engine.optionsSettings.fieldParameter];
		}

		if (aParameters !== undefined && aParameters[this.engine.optionsSettings.exactMatchParameter] !== undefined && aParameters[this.engine.optionsSettings.exactMatchParameter]) {
			searchType = "ex";
		}

		url = "https://search.emolecules.com/services/emolservicev3.php";
		action = "http://www.emolecules.com/services/continueSearch";

		xml = '<?xml version="1.0" encoding="UTF-8"?>';
		xml += '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.emolecules.com/services">';
		xml += '<SOAP-ENV:Header/>';
		xml += '<SOAP-ENV:Body>';
		xml += '<ns1:getPageInput>';
		xml += '<searchParams>';
		xml += '<q>' + aQuery + '</q>';
		xml += '<t>' + searchType + '</t>';
		if (field !== "all") {
			xml += '<sf>' + field + '</sf>';
		}
		xml += '<p>' + currentPageNumber + '</p>';
		xml += '<pp>' + previousPageNumber + '</pp>';
		xml += '</searchParams>';
		xml += '<accessHash>';
		xml += '<clientSoftware>' + this.user + '</clientSoftware>';
		xml += '<clientKey>' + this.accessKey + '</clientKey>';
		xml += '</accessHash>';
		xml += '</ns1:getPageInput>';
		xml += '</SOAP-ENV:Body>';
		xml += '</SOAP-ENV:Envelope>';

		response = this.webUtils.soapRequest(url, action, xml);

		return response;
	};

	Emolecules_Engine.prototype.structureByVID = function (aID) {

		var url, action, xml, response;

		url = "https://search.emolecules.com/services/emolservicev3.php";
		action = "http://www.emolecules.com/services/structureByVID";

		xml = '<?xml version="1.0" encoding="UTF-8"?>';
		xml += '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" ';
		xml += 'xmlns:ser="http://www.emolecules.com/services">';
		xml += '<soapenv:Header/>';
		xml += '<soapenv:Body>';
		xml += '<ser:structureByVIDInput>';
		xml += '<inputVID>' + aID + '</inputVID>';
		xml += '<accessHash>';
		xml += '<clientSoftware>' + this.user + '</clientSoftware>';
		xml += '<clientKey>' + this.accessKey + '</clientKey>';
		xml += '</accessHash>';
		xml += '</ser:structureByVIDInput>';
		xml += '</soapenv:Body>';
		xml += '</soapenv:Envelope>';

		response = this.webUtils.soapRequest(url, action, xml);

		return response;
	};

	Emolecules_Engine.prototype.runSearch = function (aMolFile, aParameters, aMaxHits, aProgressBar, aStatusInfoBar) {

		var i, response, allResponse, molFile, xmlManagement, totalHitsExpected, totalPagesExpected,
			continueResponse, startValue, endValue, currentHits, currentPage, newData, auxNewData, stringPointer,
			totalHits, actualHits, lastCurrentHits, estimateHits, continueQuery, exceptionDetail, exceptionsArray;

		//Get the initial query with results which retrievers 10 elements as maximum
		molFile = aMolFile;
		response = this.startSearch(molFile, aParameters, aMaxHits);
		allResponse = response.response.toString();
		stringPointer = allResponse.lastIndexOf('</' + this.engine.molecule.searchXMLElement + '>');

		//Get first query rows
		xmlManagement = new ManageStructureData();
		newData = xmlManagement.getElementsFromXML(allResponse, this.engine.molecule.iDArrayElement);

		//Determine the totalHits
		totalPagesExpected = xmlManagement.getElementsFromXML(allResponse, this.engine.molecule.estimatePagesOfTenElementsXMLElement);
		estimateHits = xmlManagement.getElementsFromXML(allResponse, this.engine.molecule.estimateMatchsXMLElement);
		actualHits = xmlManagement.getElementsFromXML(allResponse, this.engine.molecule.actualMatchsXMLElement);
		if (estimateHits.length > 0) {
			totalHitsExpected = estimateHits[0];
		} else if (actualHits.length > 0) {
			totalHitsExpected = actualHits[0];
		} else if (totalPagesExpected.length > 0) {
			totalHitsExpected = 10 * totalPagesExpected[0];
		}

		//Determine if it is needed to perform new searches in order to retreive all results
		if (totalHitsExpected > 9) {

			//Initialize data
			currentHits = newData.length;
			newData = [];
			currentPage = 1;
			totalHits = totalHitsExpected;
			startValue = aProgressBar.value;
			endValue = 85;

			//Get smart values
			if (aMaxHits < totalHits) {
				totalHits = aMaxHits;
			}
			if (totalHits < 20) {
				endValue = 40;
			} else if (totalHits < 30) {
				endValue = 50;
			} else if (totalHits < 40) {
				endValue = 60;
			}

			//Retrieve all hits until either we have finished or we have reached the maximum requested by the user
			while ((currentHits < totalHits) && (currentHits < aMaxHits) && (aProgressBar === undefined || !aProgressBar.wasCanceled)) {

				//Get new data into an array
				currentPage++;
				lastCurrentHits = currentHits;

				//Get the SMILES needed in continue query for structure and substructure searches
				if (aParameters !== undefined) {
					if (aParameters[this.engine.optionsSettings.fieldParameter] === this.engine.molecule.structureSearchField) {
						continueQuery = xmlManagement.getElementsFromXML(allResponse, this.engine.molecule.queryXMLElement);
						if (continueQuery.length > 0) {
							molFile = continueQuery[0];
						}
					}
				}

				//Launch the continue query
				continueResponse = this.continueSearch(molFile, aParameters, currentPage);
				allResponse = continueResponse.response;
				auxNewData = xmlManagement.getElementsFromXML(allResponse, this.engine.molecule.iDArrayElement);
				for (i = 0; i < auxNewData.length && currentHits < aMaxHits; i++) {
					newData.push(auxNewData[i]);
					currentHits++;
				}

				//Check for excetions in eMolecules side if the exception is thrown and we haven't got results.
				//This behaviour is due to a random failure in the estimated number of matching results which causes to carry out an access into a page which does not exist
				exceptionsArray = xmlManagement.getElementsFromXML(allResponse, this.engine.molecule.faultStringXMLElement);
				if (exceptionsArray.length > 0 && newData.length === 0) {
					exceptionDetail = exceptionsArray[0];
					response.exitCode = -1;
					break;
				}

				//Update Info to the users
				if (aStatusInfoBar !== undefined) {
					if (auxNewData.length === 0) {
						aStatusInfoBar.text = qsTr("Retrieving molecules ({0} of {0}) from {1} engine").format(currentHits, this.id);
					} else {
						aStatusInfoBar.text = qsTr("Retrieving molecules ({0} of {1}) from {2} engine").format(currentHits, totalHits, this.id);
					}
				}

				//Avoid endless loop if there is a problem retrieving the results
				if (lastCurrentHits === currentHits) {
					break;
				}

				//Update progress bar information
				MoleculeSearcherViewer.updateProgressBar(aProgressBar, currentHits, totalHits, startValue, endValue);
			}

			//It writes the result using the first request and the data stored in the array
			allResponse = response.response.substring(0, stringPointer);
			startValue = aProgressBar.value;
			endValue = startValue + 5;
			for (i = 0; i < newData.length; i++) {

				//It generates the xml ID element
				allResponse += "<" + this.engine.molecule.iDArrayElement + ">";
				allResponse += newData[i];
				allResponse += "</" + this.engine.molecule.iDArrayElement + ">";

				//Update progress bar information
				MoleculeSearcherViewer.updateProgressBar(aProgressBar, (i + 1), newData.length, startValue, endValue);
			}

			//Compose the response using the first request
			allResponse += response.response.substring(stringPointer + 1, response.response.length);
			response.response = allResponse;

			//It returns the abort code
			if (aProgressBar !== undefined && aProgressBar.wasCanceled) {
				response.exitCode = -3;
			}
		} else {

			//Update status info to the users
			if (aStatusInfoBar !== undefined) {
				aStatusInfoBar.text = qsTr("Retrieving molecules ({0} of {0}) from {1} engine").format(newData.length, this.id);
			}
			aProgressBar.value += 25;
		}

		return response;
	};

	Emolecules_Engine.prototype.textSearch = function (aMolFile, aParameters, aMaxHits, aProgressBar, aStatusInfoBar) {

		var response;

		response = this.runSearch(aMolFile, aParameters, aMaxHits, aProgressBar, aStatusInfoBar);

		return response;
	};

	Emolecules_Engine.prototype.structureSearch = function (aMolFile, aParameters, aMaxHits, aProgressBar, aStatusInfoBar) {

		var response, parameters;

		parameters = aParameters;
		parameters = {};
		parameters[this.engine.optionsSettings.fieldParameter] = this.engine.molecule.structureSearchField;
		parameters[this.engine.optionsSettings.exactMatchParameter] = true;
		response = this.runSearch(aMolFile, parameters, aMaxHits, aProgressBar, aStatusInfoBar);

		return response;
	};

	Emolecules_Engine.prototype.substructureSearch = function (aMolFile, aParameters, aMaxHits, aProgressBar, aStatusInfoBar) {

		var response, parameters;

		parameters = aParameters;
		parameters = {};
		parameters[this.engine.optionsSettings.fieldParameter] = this.engine.molecule.structureSearchField;
		parameters[this.engine.optionsSettings.exactMatchParameter] = false;
		response = this.runSearch(aMolFile, parameters, aMaxHits, aProgressBar, aStatusInfoBar);

		return response;
	};

	Emolecules_Engine.prototype.getMoleculeDetail = function (aArrayOfIDs, aProgressBar, aValues, aXMLResponse, aMaxEntries) {

		var i, numberOfEntries, allResponse, startValue;

		//Determine the number of rows
		numberOfEntries = aArrayOfIDs.length;
		if ((aMaxEntries !== undefined) && (!isNaN(aMaxEntries)) && (aMaxEntries < numberOfEntries)) {
			numberOfEntries = aMaxEntries;
		}

		//Initialize vars
		allResponse = aXMLResponse;
		startValue = aProgressBar.value;
		for (i = 0; i < numberOfEntries && !aProgressBar.wasCanceled; i++) {

			//Update progress bar
			MoleculeSearcherViewer.updateProgressBar(aProgressBar, (i + 1), numberOfEntries, startValue, 100);

			//Set data into the results structure data
			if (i > 0) {
				MoleculeSearcherViewer.setDetailFromStructureData(aProgressBar, aValues, allResponse, this, MoleculeSearcher.moleculeFeatureID, true);
			} else {
				MoleculeSearcherViewer.setDetailFromStructureData(aProgressBar, aValues, allResponse, this, MoleculeSearcher.moleculeFeatureID);
			}
		}

		return aValues;
	};

	Emolecules_Engine.prototype.getEncryptedID = function (aValueInArray) {

		var i, resultRegex;

		if (aValueInArray !== undefined && aValueInArray.length > 0) {
			for (i = 0; i < aValueInArray.length; i++) {
				resultRegex = aValueInArray[i].toString().match(this.engine.molecule.URLPatternToGetEncryptedID);
				if (resultRegex !== null && resultRegex.length > 1) {
					aValueInArray[i] = resultRegex[1];
				}
			}
		}
	};
}

//***********Static Attributes***************
Emolecules_Engine.user = "Mestrelab";
Emolecules_Engine.clientKey = "2eb8dac47717e08a1a19ba26d9ad86d4";
Emolecules_Engine.ID = "eMolecules";

//It sends the engine to the list of available engines
if (Env.MoleculeSearcher_Engines === undefined) {
	Env.MoleculeSearcher_Engines = [];
	MessageBox.critical("Error loading the searcher engines in Molecule Searcher");
}
Env.MoleculeSearcher_Engines.push(Emolecules_Engine.ID);
